﻿using System;
using System.Windows.Markup;
using System.Windows;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Resources;
using System.Reflection;
using System.Diagnostics;

namespace DemoLocalization.Assets.Localization
{
    /// <summary>
    /// The localize markup extension returns a localized resource
    /// for a specified resource key. 
    /// </summary>
    [MarkupExtensionReturnType(typeof(string)), Localizability(LocalizationCategory.NeverLocalize)]
    public class LocalizeExtension : MarkupExtension
    {

        /// <summary>
        /// Caches the depending target object
        /// </summary>
        private DependencyObject targetObject;

        /// <summary>
        /// Caches the depending target property
        /// </summary>
        private DependencyProperty targetProperty;

        /// <summary>
        /// Caches the resolved default type converter
        /// </summary>
        private TypeConverter typeConverter;

        private IServiceProvider serviceProvider;

        /// <summary>
        /// Initializes a new instance of the <see cref="TranslateExtension"/> class.
        /// </summary>
        public LocalizeExtension()
        {
            LocalizationManager.CultureChanged += LocalizationManager_CultureChanged;
        }

        public LocalizeExtension(string staticResouceId)
        {
            this.Static = staticResouceId;
            LocalizationManager.CultureChanged += LocalizationManager_CultureChanged;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="LocalizeExtension"/> is reclaimed by garbage collection.
        /// </summary>
        ~LocalizeExtension()
        {
            LocalizationManager.CultureChanged -= LocalizationManager_CultureChanged;
        }

        /// <summary>
        /// Handles the CultureChanged event of the LocalizationManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void LocalizationManager_CultureChanged(object sender, EventArgs e)
        {
            UpdateTarget();            
        }

        public string Static { get; set; }

        /// <summary>
        /// Gets or sets the resource key.
        /// </summary>
        /// <value>The key.</value>
        public string Id { get; set; }

        /// <summary>
        /// Optional Resource Set Name. If not provided it's
        /// assumed you want to access the global Resources
        /// resource set in Properties.Resources
        /// </summary>
        public string ResourceSet { get; set; }

        /// <summary>
        /// Gets or sets a format string that is used to format the value
        /// </summary>
        /// <value>The format.</value>
        public string Format { get; set; }

        /// <summary>
        /// Gets or sets the converter.
        /// </summary>
        /// <value>The converter.</value>
        public IValueConverter Converter { get; set; }

        /// <summary>
        /// Updates the target.
        /// </summary>
        internal void UpdateTarget()
        {
            if (targetObject != null && targetProperty != null)
            {
                targetObject.SetValue(targetProperty, ProvideValueInternal());
            }
        }

        /// <summary>
        /// Returns the localized value
        /// </summary>
        /// <param name="serviceProvider">Object that can provide services for the markup extension.</param>
        /// <returns>
        /// The object value to set on the property where the extension is applied.
        /// </returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            // Resolve the depending object and property
            if (targetObject == null)
            {
                var targetHelper = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                targetObject = targetHelper.TargetObject as DependencyObject;
                targetProperty = targetHelper.TargetProperty as DependencyProperty;
                typeConverter = TypeDescriptor.GetConverter(targetProperty.PropertyType);
            }
            this.serviceProvider = serviceProvider;

            return ProvideValueInternal();
        }

        [Conditional("DEBUG")]
        private void ValidateResourceValueOnDebug(object value)
        {
            if (value == null)
            {
                throw new Exception(
                    string.Format("Invalid resource key: \r\n" +
                                  "  Id = {0}\r\n" +
                                  "  ResourceSet = {1}\r\n" +
                                  "  Static = {2}\r\n",
                                  this.Id ?? "null",
                                  this.ResourceSet ?? "null",
                                  this.Static ?? "null"));
            }
        }

        private object ProvideValueInternal()
        {
            // Get the localized value
            object localizedValue = null;

            if (string.IsNullOrEmpty(this.Static))
            {
                ResourceManager resourceManager = LocalizationManager.GetResourceManager(this.ResourceSet);
                if (resourceManager != null)
                {
                    localizedValue = resourceManager.GetObject(this.Id);
                }
            }
            else
            {
                try
                {
                    // Parse Static=properties:Resources.HelloWorld 
                    int index = this.Static.LastIndexOf('.');
                    if (index == -1)
                    {
                        throw new ArgumentException("Static");
                    }

                    // resolve properties:Resources
                    string typeName = this.Static.Substring(0, index);
                    string propertyName = this.Static.Substring(index + 1);

                    IXamlTypeResolver service = this.serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
                    Type memberType = service.Resolve(typeName.Replace(".", "+"));

                    localizedValue = memberType.GetProperty(
                        propertyName,
                        BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy
                    ).GetValue(memberType, null);
                }
                catch
                {
                    // ignore retrieval errors
                    // returns default localozation value
                }
            }

            this.ValidateResourceValueOnDebug(localizedValue);

            // fail type conversions silently and write to trace output
            try
            {
                // Convert if a type converter is availalbe
                if (localizedValue != null && this.Converter == null && typeConverter != null && typeConverter.CanConvertFrom(localizedValue.GetType()))
                {
                    localizedValue = typeConverter.ConvertFrom(localizedValue);
                }
                // Apply a type converter if one was specified
                if (localizedValue != null && Converter != null)
                {
                    localizedValue = this.Converter.Convert(localizedValue, targetProperty.PropertyType, null, CultureInfo.CurrentCulture);
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Cannot convert localization value Id={0}, ResourceSet={1} or Static={2} using specified coverter. {3}",
                    Id,
                    ResourceSet,
                    Static,
                    ex.Message));

                localizedValue = null;
            }

            // If no fallback value is available, return the key
            if (localizedValue == null)
            {
                if (targetProperty != null)// && targetProperty.PropertyType == typeof(string))
                {
                    // Return the key surrounded by question marks for string type properties
                    if (!string.IsNullOrEmpty(this.Static))
                    {
                        localizedValue = string.Concat("??? ", this.Static, " ???");
                    }
                    else
                    {
                        localizedValue = string.Concat("??? ", this.Id, " ???");
                    }
                }
                else
                {
                    // Return the UnsetValue for all other types of dependency properties
                    return DependencyProperty.UnsetValue;
                }
            }

            if (Converter != null)
            {
                localizedValue = Converter.Convert(localizedValue, targetProperty.PropertyType, null, CultureInfo.CurrentCulture);
            }

            // Format the value if a format string is provided and the type implements IFormattable
            if (localizedValue is IFormattable && Format != null)
            {
                ((IFormattable)localizedValue).ToString(Format, CultureInfo.CurrentCulture);
            }

            return localizedValue;
        }

    }
}